﻿using System;
using System.Collections.Generic;
using System.Concurrency;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.ServiceModel;
using Collab.Modules.Editing;
using Collab.Modules.Editing.Interfaces;
using Collab.Modules.Session;
using Collab.Services.Remote;
using Collab.Services.Remote.Contracts;
using Collab.Services.Remote.Entities;
using System.Threading;
using System.Threading.Tasks;

namespace Collab.Modules.Sessions
{
    public class CollaborationClientSession : CollaborationSession
    {
        private readonly IRemoteCollaborationCallback callback;
        private readonly RemoteCollaborationServiceProxy proxy;

        public CollaborationClientSession(string name, DocumentTracker tracker, StateTracker stateTracker, string ip, SolutionBuilder solutionBuilder)
            : base(name, tracker, stateTracker, solutionBuilder)
        {

            if (string.IsNullOrEmpty(ip))
                throw new ArgumentException("ip");

            SetupViewEvents(this.documentTracker);

            this.callback = new RemoteCollaborationCallback(); 
            this.proxy = new RemoteCollaborationServiceProxy(this.user,
                               callback,
                               CollaborationBinding.CreateCollaborationBinding(),
                               new EndpointAddress("net.tcp://" + ip + ":8888/RemoteCollaborationService"));
            this.disposable.Add(this.proxy);

            SetupCommonServiceOperations(this.callback, this.proxy);

            this.proxy.GetCurrentUsers().Run(user => AddUser(user));
            

            var solution = this.proxy.GetSolutionInformation();
            if (solution == null)
                this.callback.WhenOpenedSolution.Subscribe(sol => this.solutionBuilder.BuildSolution(sol, (fileRequests, readyCallback) => DownloadFiles(fileRequests, this.user.Id).Subscribe(x => { }, readyCallback)));
            else
                this.solutionBuilder.BuildSolution(solution, (fileRequests, readyCallback) => DownloadFiles(fileRequests, this.user.Id).Subscribe(x => { }, () =>
                {
                    readyCallback();
                    this.SessionState = Session.SessionState.Ready;
                }));

            //TODO: Should this really exist? Doesn't work properly
            this.documentTracker.ActiveViews.Run(docView =>
                {
                    Debug.WriteLine("Requested: " + docView.Name);
                    IEditBuffer editBuffer = new VSXEditBuffer(docView.View.TextBuffer, this.user.Id, docView.Name, this.proxy.GetCurrentRevision(docView.Name));
                    editBuffer
                        .Where(change => change.Sender == this.user.Id)
                        .Subscribe(SendChange);
                    this.editBuffers[docView.Name] = editBuffer;
                });

            this.disposable.Add(this.callback.WhenFileAdded.Subscribe(file => this.solutionBuilder.AddFile(file, (fileRequests, userID, readyCallback) => DownloadFiles(fileRequests, userID).Subscribe(x => { }, readyCallback))));

        }

        private void SendChange(BufferUpdate update)
        {
            //Task.Factory.StartNew(() =>
            //{
                update.UpdateIndex = this.updateIndex++;
                this.proxy.SendChange(update);
            //});
        }


        private IObservable<Unit> DownloadFiles(IDictionary<string, string> fileRequests, Guid userID)
        {
            return Observable.CreateWithDisposable<Unit>(o => Scheduler.NewThread.Schedule(() =>
                {
                    var files = fileRequests.Select(x =>
                    this.callback.FileChunkReceived
                        .Where(chunk => chunk.FileName == x.Key)
                        .TakeWhile(chunk => !chunk.EOF)
                        .Aggregate(new List<FileChunk>(), (chunks, chunk) =>
                        {
                            chunk.FileName = x.Value;
                            chunks.Add(chunk);
                            return chunks;
                        }))
                    .Merge()
                    .Subscribe(chunks =>
                    {
                        var dir = Path.GetDirectoryName(chunks.First().FileName);
                        if (!Directory.Exists(dir))
                            Directory.CreateDirectory(dir);

                        using (var fileStream = new FileStream(chunks.First().FileName, FileMode.Create))
                        {
                            chunks.Run(x => fileStream.Write(x.Data, 0, x.Data.Length));
                        }
                    }, () =>
                    {
                        o.OnNext(new Unit());
                        o.OnCompleted();
                    });

                    fileRequests.Run(request =>
                        this.proxy.RequestFile(new FileRequest { FileName = request.Key, Sender = this.user.Id }));
                }));
        }

        private void SetupViewEvents(DocumentTracker tracker)
        {
            this.disposable.Add(tracker);
            this.disposable.Add(this.documentTracker.WhenViewActivated.ObserveOnDispatcher().Subscribe(docView =>
            {
                this.proxy.SendUserStatus(new UserStatus(this.user.Id, docView.Name));
                IEditBuffer editBuffer = new VSXEditBuffer(docView.View.TextBuffer, this.user.Id, docView.Name, this.proxy.GetCurrentRevision(docView.Name));
                editBuffer
                    .Where(change => change.Sender == this.user.Id)
                    .Subscribe(SendChange);
                this.editBuffers[docView.Name] = editBuffer;
            }));

            this.disposable.Add(this.documentTracker.WhenSelectedViewChanged.ObserveOnDispatcher().Subscribe(docView =>
            {
                if (this.proxy != null && docView != null)
                    this.proxy.SendUserStatus(new UserStatus(this.user.Id, docView.Name));
            }));

            this.disposable.Add(this.documentTracker.WhenViewClosed.ObserveOnDispatcher().Subscribe(docView =>
            {
                this.editBuffers[docView.Name].Dispose();
                IEditBuffer buffer;
                this.editBuffers.TryRemove(docView.Name, out buffer);
            }));
        }

    }
}
